﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Principal;
using System.Text;
using System.Threading;

namespace Nizm0.Chat.Client
{
    public class Client
    {
        //private TcpListener _listener = null;
        private TcpClient _client = null;
        private bool _isconnected = false;

        public bool Isconnected
        {
            get { return _isconnected; }
            set { _isconnected = value; }
        }
        private BinaryReader _r = null;
        private BinaryWriter _w = null;
        private object _olock = new object();

        private Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        private int _clientPortChat, _clientPortFileSend, _serverPortChat = 8000, _serverPortFileSend;
        private string _serverIP, _clientIP;
        private string _username = WindowsIdentity.GetCurrent().Name;
        private List<Messages.User> _userList = new List<Messages.User>();

        public int Port
        {
            get { return _clientPortChat; }
        }
        public string Ip
        {
            get { return _serverIP; }
        }

        Thread _thrConnection;
        Thread _thrReceiving;

        public Client()
        {
            _serverIP = "127.0.0.1";
            _clientPortChat = 8000;
            _clientPortFileSend = 8001;
            //this.Start();
        }
        public Client(IPEndPoint ip):this(ip,"test")
        {
            //_serverIP = ip.Address.ToString();
            //_clientPortChat = ip.Port;
            //_clientPortFileSend = ip.Port + 1;
            //this.Start();
        }
        public Client(IPEndPoint ip, string userName)
        {
            _serverIP = ip.Address.ToString();
            _clientPortChat = ip.Port;
            _clientPortFileSend = ip.Port + 1;
            _username = userName;
            //this.Start();
        }

        #region Delegates

        public delegate void StatusChangeEvenHandler();
        public event StatusChangeEvenHandler BeforeConnection;
        public event StatusChangeEvenHandler AfterConnection;
        public event StatusChangeEvenHandler NoResponseConnection;
        public event StatusChangeEvenHandler ConnectionClosed;

        public delegate void MessageEvenHandler(Messages.TextMessage message);
        public event MessageEvenHandler MessageSended;
        public event MessageEvenHandler MessageReceived;

        public delegate Messages.User UserAddEventHandler(Messages.LoginMessage message);
        public event UserAddEventHandler OnUserAdd;
        #endregion

        #region Methods

        public void Print(string str)
        {
            if (ConnectionClosed != null) ConnectionClosed();
            Console.WriteLine(DateTime.Now.ToLongTimeString() + ": " + str);
        }
        public void Print(string str, Messages.MessageType type)
        {
            switch (type)
            {
                case Messages.MessageType.client:
                    {
                        Console.WriteLine(DateTime.Now.ToLongTimeString() + ":\t" + str);
                        break;
                    }
                case Messages.MessageType.server:
                    {
                        Console.WriteLine(DateTime.Now.ToLongTimeString() + " : server\n" + str);
                        break;
                    }
                case Messages.MessageType.system:
                    {
                        Console.WriteLine(DateTime.Now.ToLongTimeString() + " : system\n" + str);
                        break;
                    }
                default:
                    {
                        Console.WriteLine(DateTime.Now.ToLongTimeString() + " -> " + type.ToString() + " : " + str);
                        break;
                    }
            }
        }

        //public void Start2()
        //{
        //    Thread.Sleep(100);
        //    _thrConnection = new Thread(Connection);
        //    _thrConnection.Start();
        //    //Thread.Sleep(600);
        //}

        //public void Connection()
        //{
        //    lock (_olock)
        //    {
        //        _client = new TcpClient();
        //        Console.WriteLine("Początek");
        //        if (BeforeConnection != null)
        //            BeforeConnection();
        //        Print("Próbuję się połączyć z Serwerem " + _serverIP);
        //        _client.Connect(IPAddress.Parse(_serverIP), _clientPortChat);
        //        Print("Połączenie nawiązane, czekam  na odpowiedź");
        //        NetworkStream stream = _client.GetStream();
        //        _w = new BinaryWriter(stream);
        //        _r = new BinaryReader(stream);
        //        _w.Write(Messages.MessagesFromKlient.Require);
        //        Console.WriteLine("wysłanie wiadomości połączenia");
        //        if (_r.ReadString() == Messages.MessagesFromServer.OK)
        //        {
        //            if (AfterConnection != null)
        //                AfterConnection();
        //            Print("Połączono");
        //            _isconnected = true;
        //            //ThreadPool.QueueUserWorkItem(Receiving);
        //            ParameterizedThreadStart thrstart = new ParameterizedThreadStart(Receiving);
        //            _thrReceiving = new Thread(thrstart);
        //            Console.WriteLine("uruchomienie oczekiwania na odpowiedz");
        //            _thrReceiving.Start();
        //        }
        //        else
        //        {
        //            if (NoResponseConnection != null)
        //                NoResponseConnection();
        //            Print("Brak odpowiedzi, rozłaczono");
        //            _isconnected = false;
        //            if (_client != null) _client.Close();
        //        }
        //    }
        //}

        public void Disconnect()
        {
            if (ConnectionClosed != null)
                ConnectionClosed();

            if (_thrReceiving != null && _thrReceiving.IsAlive)
                _thrReceiving.Abort();
            if (_thrConnection != null && _thrConnection.IsAlive)
            {
                _thrConnection.Join();
                _thrConnection.Abort();
            }
            if (_isconnected)
            {
                _w.Write(Messages.MessagesFromKlient.Disconnect);
                _client.Close();
                _isconnected = false;
            }
            Print("Rozłączono");
        }

        //private void Receiving(Object o)
        //{
        //    string str;
        //    while ((str = _r.ReadString()) != Messages.MessagesFromServer.Disconnect)
        //    {
        //        if (MessageReceived != null)
        //            MessageReceived(str);
        //        Print("Server -> " + str);
        //        Console.WriteLine("odebrałem wiadomość");
        //    }
        //    Print("Rozłączono");
        //    _isconnected = false;
        //    if (_client != null) _client.Close();

        //}

        //public void Send(string str)
        //{
        //    if (!_isconnected && !_thrConnection.IsAlive)
        //    {
        //        Console.WriteLine("nie połączony wznawiam połączenia");
        //        _thrConnection = null;
        //        _thrConnection = new Thread(Connection);
        //        _thrConnection.Start();
        //    }
        //    else
        //    {
        //        Console.WriteLine("czekam na zakończenia wątku łączenia");
        //        _thrConnection.Join();
        //        if (MessageSended != null)
        //            MessageSended(str);
        //        Console.WriteLine(_username + "=>" + str);
        //        Console.WriteLine("wysyłam wiadomość");
        //        _w.Write(_username + "=>" + str);

        //    }
        //}
        public void Send(Messages.IMessage o)
        {
            if (!client.Connected)
                client.Connect(new IPEndPoint(IPAddress.Parse(_serverIP), _serverPortChat));

            byte[] buffer = SerializeObject(o);
            int read = buffer.Length;
            client.Send(buffer, 0, read, SocketFlags.None);
            //client.Receive(
            //client.rece

        }
        public void Send(Stream stream)
        {
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            client.Connect(new IPEndPoint(IPAddress.Parse(_serverIP), _clientPortFileSend));
            using (Stream s = stream)// File.OpenRead("Foo.txt"))
            {
                int read = 0, count = 0;
                while (read < s.Length)
                {
                    byte[] buffer = new byte[8192];
                    count = s.Read(buffer, 0, buffer.Length);
                    client.Send(buffer, 0, count, SocketFlags.None);
                    read += count;
                }
            }
        }
        public void Send(FileStream fstrim)
        {
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            client.Connect(new IPEndPoint(IPAddress.Parse(_serverIP), _clientPortFileSend));
            //połączenie ustanowione
            using (Stream s = fstrim)// File.OpenRead("Foo.txt"))
            {
                int read = 0, count = 0;
                while (read < s.Length)
                {
                    byte[] buffer = new byte[8192];
                    count = s.Read(buffer, 0, buffer.Length);
                    client.Send(buffer, 0, count, SocketFlags.None);
                    read += count;
                }
            }
            //Console.ReadLine();
        }

        public void ChangeConnectionData(string ip)
        {
            _serverIP = ip;
        }
        public void ChangeConnectionData(int port)
        {
            _clientPortChat = port;
        }
        public void ChangeConnectionData(string ip, int port)
        {
            _serverIP = ip;
            _clientPortChat = port;

        }
        public void ChangeConnectionData(IPEndPoint remoteEP)
        {
            _serverIP = remoteEP.Address.ToString();
            _clientPortChat = remoteEP.Port;
        }
        #endregion

        #region Reciving

        static bool _running = true;
        public void Recive()
        {
            //Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //client.Bind(new IPEndPoint(IPAddress.Any, _clientPortChat));
            client.Listen(int.MaxValue);
            while (_running)
            {
                if (!client.Connected)
                    client = client.Accept();

                byte[] buffer = new byte[8192];
                client.Receive(buffer);
                ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessClient), client);
            }
            Console.ReadLine();
        }
        public void ProcessClient(object state)
        {
            using (Socket client = state as Socket)
            {
                //coś
                byte[] buffer = new byte[8192];
                int count = 0;
                bool continueOrNot = false;
                string message = string.Empty;
                do
                {
                    count = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
                    Console.WriteLine("{0}\n{1}", client.RemoteEndPoint, client.LocalEndPoint);
                    //dekodowanie danych
                    //count powie ile bajtów już odebrałeś
                    Stream s = new MemoryStream(buffer);
                    var o = DeserializeObject(s);
                    //Console.WriteLine(o.GetType().ToString());
                    //var o1 = Convert.ChangeType(o, typeof(Messages.IMessage));
                    //string test = o.GetType().ToString();
                    //if (o.GetType() == typeof(Messages.Message))
                    //{

                    //}
                    //Type a = o.GetType();
                    //Print(o, client);
                    //Thread.Sleep(1);
                    switch (o.GetType().ToString())
                    {
                        case "Messages.TextMessage":
                            {
                                //((Messages.TextMessage)o).Get();
                                if (MessageReceived != null)
                                {
                                    MessageReceived((Messages.TextMessage)o);
                                }
                                //Messages.TextMessage textmessage = (Messages.TextMessage)o;
                                //Print((Messages.TextMessage)o, client);
                                //messageObject.ReciveDate = DateTime.Now;
                                //Console.WriteLine("Wiadomosc od ip: {0}", client.RemoteEndPoint.ToString());
                                //Console.WriteLine(messageObject.MessageText + " \"" + (messageObject.ReciveDate - messageObject.SendDate).Milliseconds + "\"");
                                break;
                            }
                        case "Messages.LoginMessage":
                            {
                                _userList.Add(((Messages.LoginMessage)o).Sender);
                                if(OnUserAdd!=null)
                                {
                                    OnUserAdd((Messages.LoginMessage)o);
                                }
                                //Messages.LoginMessage loginObject = (Messages.LoginMessage)o;
                                //Print((Messages.LoginMessage)o, client);

                                //loginObject.MessageUser.Ip = (IPEndPoint)client.RemoteEndPoint;
                                //Console.WriteLine("Zalogowany użytkonwik: {0}\no ip: {1}:{2}", loginObject.MessageUser.ToString(), loginObject.MessageUser.Ip.Address.ToString(), loginObject.MessageUser.Ip.Port.ToString());
                                //_userList.Add(loginObject.MessageUser);
                                break;
                            }
                    }
                    message += Encoding.ASCII.GetString(buffer, 0, count);

                    continueOrNot = count == buffer.Length;
                } while (continueOrNot);
            }
        }

        private byte[] SerializeObject(Messages.IMessage o)
        {
            MemoryStream stream = new MemoryStream();

            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            formatter.Serialize(stream, o);
            //stream.Close();
            return stream.GetBuffer();
        }
        private Messages.Message DeserializeObject(Stream stream)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

            stream.Position = 0;
            Messages.Message o = (Messages.Message)formatter.Deserialize(stream);
            Console.WriteLine(o.GetType().ToString());
            //stream.Close();
            return o;
        }

        #endregion

        //#region From net

        //ManualResetEvent allDone = new ManualResetEvent(false);

        ///// <summary>
        ///// Starts the client and attempts to send an object to the server
        ///// </summary>
        //public void Start()
        //{
        //    Console.Out.WriteLine("Waiting for connection...");
        //    allDone.Reset();
        //    Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //    sender.BeginConnect(new IPEndPoint(IPAddress.Loopback, 1440), Connect, sender);
        //    allDone.WaitOne(); //halts this thread until the connection is accepted

        //}

        ///// <summary>
        ///// Starts when the connection was accepted by the remote hosts and prepares to send data
        ///// </summary>        
        //public void Connect(IAsyncResult result)
        //{
        //    Messages.TextMessage status = new Messages.TextMessage("Hello webs");
        //    status.Socket = (Socket)result.AsyncState;
        //    status.Socket.EndConnect(result);
        //    //status.MessageText = "Hello webs";
        //    byte[] buffer = status.Serialize(); //fills the buffer with data
        //    status.Socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, Send, status);
        //}

        ///// <summary>
        ///// Ends sending the data, waits for a readline until the thread quits
        ///// </summary>        
        //public void Send(IAsyncResult result)
        //{
        //    Messages.TextMessage status = (Messages.TextMessage)result.AsyncState;
        //    int size = status.Socket.EndSend(result);
        //    Console.Out.WriteLine("Send data: " + size + " bytes.");
        //    Console.ReadLine();
        //    allDone.Set(); //signals thread to continue so it sends another message
        //} 
        //#endregion
    }
}
